package com.ruoyi.framework.security.service

import com.ruoyi.common.constant.*
import com.ruoyi.common.utils.*
import com.ruoyi.common.utils.ip.AddressUtils
import com.ruoyi.common.utils.ip.IpUtils
import com.ruoyi.common.utils.uuid.IdUtils
import com.ruoyi.framework.redis.RedisCache
import com.ruoyi.framework.security.LoginUser
import eu.bitwalker.useragentutils.UserAgent
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.util.concurrent.*
import javax.servlet.http.HttpServletRequest

/**
 * token验证处理
 *
 * @author ruoyi
 */
@Component
open class TokenService {
    // 令牌自定义标识
    @Value("\${token.header}")
    private val header: String? = null

    // 令牌秘钥
    @Value("\${token.secret}")
    private val secret: String? = null

    // 令牌有效期（默认30分钟）
    @Value("\${token.expireTime}")
    private val expireTime = 0

    @Autowired
    private val redisCache: RedisCache? = null

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    fun getLoginUser(request: HttpServletRequest): LoginUser? {
        // 获取请求携带的令牌
        val token = getToken(request)
        when {
            StringUtils.isNotEmpty(token) -> {
                try {
                    val claims = parseToken(token!!)
                    // 解析对应的权限以及用户信息
                    val uuid = claims[Constants.LOGIN_USER_KEY] as String?
                    val userKey = getTokenKey(uuid)
                    return redisCache!!.getCacheObject(userKey) as LoginUser?
                } catch (_: Exception) {
                }
            }
        }
        return null
    }

    /**
     * 设置用户身份信息
     */
    fun setLoginUser(loginUser: LoginUser) {
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.token)) {
            refreshToken(loginUser)
        }
    }

    /**
     * 删除用户身份信息
     */
    fun delLoginUser(token: String?) {
        StringUtils.isNotEmpty(token).let {
            val userKey = getTokenKey(token)
            redisCache!!.deleteObject(userKey)
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    fun createToken(loginUser: LoginUser): String {
        val token = IdUtils.fastUUID()
        loginUser.token = token
        setUserAgent(loginUser)
        refreshToken(loginUser)
        val claims: MutableMap<String, Any> = HashMap()
        claims[Constants.LOGIN_USER_KEY] = token
        return createToken(claims)
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param token 令牌
     * @return 令牌
     */
    fun verifyToken(loginUser: LoginUser) {
        val expireTime = loginUser.expireTime!!
        val currentTime = System.currentTimeMillis()
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser)
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    fun refreshToken(loginUser: LoginUser) {
        loginUser.loginTime = System.currentTimeMillis()
        loginUser.expireTime = loginUser.loginTime!! + expireTime * MILLIS_MINUTE
        // 根据uuid将loginUser缓存
        val userKey = getTokenKey(loginUser.token)
        redisCache!!.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES)
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    fun setUserAgent(loginUser: LoginUser) {
        val userAgent = UserAgent.parseUserAgentString(ServletUtils.request.getHeader("User-Agent"))
        val ip = IpUtils.getIpAddr(ServletUtils.request)
        loginUser.ipaddr = ip
        loginUser.loginLocation = AddressUtils.getRealAddressByIP(ip)
        loginUser.browser = userAgent.browser.getName()
        loginUser.os = userAgent.operatingSystem.getName()
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private fun createToken(claims: Map<String, Any>): String {
        return Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS512, secret).compact()
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private fun parseToken(token: String): Claims {
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .body
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    fun getUsernameFromToken(token: String): String {
        val claims = parseToken(token)
        return claims.subject
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    private fun getToken(request: HttpServletRequest): String? {
        var token = request.getHeader(header)
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "")
        }
        return token
    }

    private fun getTokenKey(uuid: String?): String {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid
    }

    companion object {
        protected const val MILLIS_SECOND: Long = 1000
        protected const val MILLIS_MINUTE: Long = 60 * MILLIS_SECOND
        private const val MILLIS_MINUTE_TEN = 20 * 60 * 1000L
    }
}
